﻿// COPYRIGHT 2012, Justin Michalczak
//
// This file is licenced to the public under the Apache
// License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;

using CopperVineSoftware.Stackr.Parser;
using CopperVineSoftware.Stackr.Parser.Lexemes;

namespace CopperVineSoftware.Stackr.Compiler.DCPU16Assembly
{
    /// <summary>Main class for DCPU-16 trans-compilation.</summary>
    public class DCPU16AssemblyCompiler
    {
        static void Main(string[] args)
        {
            LinkedList<string> inputFiles = new LinkedList<string>();
            string outputFile = null;

            // Parse arguments.
            for (int argIndex = 0; argIndex < args.Length; ++argIndex)
            {
                string argument = args[argIndex];
                if (argument == "-o")
                {
                    if (outputFile != null)
                    {
                        Console.Error.WriteLine("Option \"-o\" may only be provided once.");
                        return;
                    }
                    else if (argIndex == (args.Length - 1))
                    {
                        Console.Error.WriteLine("Option \"-o\" requires a file path to output to.");
                        return;
                    }
                    else
                    {
                        outputFile = args[++argIndex];
                    }
                }
                else
                {
                    inputFiles.AddLast(argument);
                }
            }

            if (inputFiles.Count <= 0)
            {
                Console.Error.WriteLine("No input files provided.");
                return;
            }

            // Concatenate all input files together.
            string concatenatedInput = "";
            foreach(string filePath in inputFiles)
            {
                try
                {
                    using (StreamReader fileReader = File.OpenText(filePath))
                    {
                        string fileContents = fileReader.ReadToEnd();
                        concatenatedInput += fileContents;
                    }
                }
                catch (FileNotFoundException)
                {
                    Console.Error.WriteLine("Could not open input file \"{0}\" for reading.", filePath);
                    return;
                }
            }

            try
            {
                // Compile concatentated input as a program.
                IEnumerable<string> instructions = Compile(concatenatedInput);

                // Output to screen or file.
                if (outputFile == null)
                {
                    Console.WriteLine(string.Join("\n", instructions));
                }
                else
                {
                    using (FileStream fileStream = File.OpenWrite(outputFile))
                    {
                        using (StreamWriter fileWriter = new StreamWriter(fileStream))
                        {
                            foreach (string instruction in instructions)
                                fileWriter.WriteLine(instruction);
                        }
                    }
                }
            }
            catch (NoMainFunctionDefinedException)
            {
                Console.Error.WriteLine("No function named \"main\" found.");
                return;
            }
            catch (SyntaxException exception)
            {
                Console.Error.WriteLine(exception.Message);
                return;
            }
        }

        /// <summary>Compiles a Stackr program file.</summary>
        /// <param name="filePath">Path to the file to compile.</param>
        /// <returns>The compiled sequence of DCPU-16 instructions.</returns>
        /// <exception cref="ArgumentNullException">If the <paramref name="filePath"/> parameter is <c>null</c> or empty.</exception>
        public static IEnumerable<string> CompileFile(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
                throw new ArgumentNullException("filePath");

            using (StreamReader fileReader = File.OpenText(filePath))
            {
                string fileContents = fileReader.ReadToEnd();
                return Compile(fileContents);
            }
        }

        /// <summary>Compiles a Stackr program from text.</summary>
        /// <param name="programText">Text of program to compile.</param>
        /// <returns>The compiled sequence of DCPU-16 instructions.</returns>
        /// <exception cref="ArgumentNullException">If the <paramref name="programText"/> parameter is <c>null</c>.</exception>
        public static IEnumerable<string> Compile(string programText)
        {
            if (programText == null)
                throw new ArgumentNullException("programText");

            Program program = Parser.Parser.Parse(programText);
            return Compile(program);
        }

        /// <summary>Compiles a Stackr program.</summary>
        /// <param name="program">Program to compile.</param>
        /// <returns>The compiled sequence of DCPU-16 instructions.</returns>
        /// <exception cref="ArgumentNullException">If the <paramref name="program"/> parameter is <c>null</c>.</exception>
        public static IEnumerable<string> Compile(Program program)
        {
            if (program == null)
                throw new ArgumentNullException("program");

            return ProgramCompiler.Compile(program);
        }
    }
}
